#include "graphics/sprite.h"
#include "graphics/sprite_private.h"
#include "graphics/sprite_events.h"
#include "graphics/sprite_events_private.h"

#include "system/window.h"

void _esdlSpriteInit(EsdlSprite* sprite) {
    sprite->surface = NULL;
    sprite->image = NULL;
    sprite->x = 0;
    sprite->y = 0;
    sprite->zindex = 1;
    sprite->focus = false;
    sprite->over = false;
    sprite->show = true;
    sprite->parent = NULL;
    sprite->children = NULL;
    sprite->capturingListeners = NULL;
    sprite->bubblingListeners = NULL;
    sprite->data = NULL;
    sprite->visiblePart.x = 0;
    sprite->visiblePart.y = 0;
    sprite->visiblePart.w = 0;
    sprite->visiblePart.h = 0;
    esdlAddListener(sprite, ON_FOCUS, _setFocus, true);
    esdlAddListener(sprite, ON_BLUR, _removeFocus, true);

    //_esdlAddListener(sprite, ON_MOUSE_UP , updateFocus, true);
}

EsdlSprite* _esdlCreateScreenSprite(SDL_Surface* surface) {
    EsdlSprite* sprite = malloc(sizeof(EsdlSprite));

    _esdlSpriteInit(sprite);
    sprite->surface = SDL_CreateRGBSurface(SDL_SRCCOLORKEY, surface->w, surface->h, 32, 0, 0, 0, 0);
    sprite->focus = true;

    sprite->visiblePart.w = surface->w;
    sprite->visiblePart.h = surface->h;

    return sprite;
}

EsdlSprite* esdlCreateSprite(unsigned int width, unsigned int height) {
    EsdlSprite* sprite = malloc(sizeof(EsdlSprite));

    _esdlSpriteInit(sprite);
    sprite->surface = SDL_CreateRGBSurface(SDL_SRCCOLORKEY, width, height, 32, 0, 0, 0, 0);
    esdlSetSpriteParent(sprite, esdlGetScreen());

    sprite->visiblePart.w = width;
    sprite->visiblePart.h = height;

    return sprite;
}

EsdlSprite* esdlLoadSpriteFromFile(const char* path) {
    EsdlSprite* sprite = malloc(sizeof(EsdlSprite));

    _esdlSpriteInit(sprite);
    sprite->surface = IMG_Load(path);

    if (sprite->surface == NULL) {
        free(sprite);
        return NULL;
    }

    esdlSetSpriteParent(sprite, esdlGetScreen());
    sprite->visiblePart.w = sprite->surface->w;
    sprite->visiblePart.h = sprite->surface->h;

    return sprite;
}

EsdlSprite * esdlLoadSpriteFromImage(EsdlImage* image) {
    EsdlSprite* sprite = malloc(sizeof (EsdlSprite));

    _esdlSpriteInit(sprite);
    sprite->image = image;

    esdlSetSpriteParent(sprite, esdlGetScreen());
    sprite->visiblePart.w = sprite->image->surface->w;
    sprite->visiblePart.h = sprite->image->surface->h;

    return sprite;
}

/**
 *	ATTENTION : TODO Bug lors de la libération des listeners ! (seg fault...)
 */
void esdlDeleteSprite(EsdlSprite* sprite) {
    if (sprite == NULL)
        return;

    /*
    while (sprite->capturingListeners != NULL) {
        free((EsdlListener *)removeFirst(&(sprite->capturingListeners)));
    }

    while (sprite->bubblingListeners != NULL) {
        free((EsdlListener *)removeFirst(&(sprite->bubblingListeners)));
    }//*/

    if (sprite->parent != NULL) {
        removeElement(&(sprite->parent->children), (void*) sprite);
    }

    /*
    while (sprite->children != NULL) {
        removeFirst(&(sprite->children));
    }//*/

    if (sprite->surface != NULL) {
        SDL_FreeSurface(sprite->surface);
        sprite->surface = NULL;
    }

    free(sprite);
}

void _esdlInsertChild(EsdlLinkedList* children, EsdlSprite* child) {
    EsdlLinkedListElement* element = NULL;
    addFirst(&element, (void*) child);

    if (*children == NULL) {
        *children = element;
        return;
    }

    // On insère l'enfant dans la liste des enfants en fonction de la valeur du zindex
    // On a donc une liste d'enfants triés par zindex
    EsdlLinkedList tmp = *children;
    while (tmp != NULL) {
        if (((EsdlSprite *) tmp->data)->zindex >= child->zindex) {
            element->next = tmp;
            element->previous = tmp->previous;
            (tmp->previous == NULL) ? (*children = element) : (tmp->previous->next = element);
            tmp->previous = element;
            return;
        }
        if(tmp->next == NULL) {
            tmp->next = element;
            element->previous = tmp;
            return;
        }
        tmp = tmp->next;
    }
    //addLast(children, child);           // Euuu pourquoi ??? :/ (sans ça les boutons ne marchent plus :/ à debugger !)
}

bool esdlSetSpriteParent(EsdlSprite* sprite, EsdlSprite* parentSprite) {
    if (parentSprite == NULL)
        parentSprite = esdlGetScreen();

    // Prévention bouclage
    struct EsdlSprite* tmp = parentSprite;
    while (tmp->parent != NULL) {
        if (tmp->parent == sprite)
            return false;
        tmp = tmp->parent;
    }

    // On supprime le lien hiérarchique entre le sprite courant et son ancien père
    if (sprite->parent != NULL) {
        removeElement(&(sprite->parent->children), (void*) sprite);
    }
    
    // On attribut au sprite courant, un nouveau père
    sprite->parent = parentSprite;
    _esdlInsertChild(&(parentSprite->children), sprite);

    return true;
}

void esdlSetSpritePosition(EsdlSprite* sprite, int x, int y) {
    sprite->x = x;
    sprite->y = y;
}

int esdlGetSpriteXPosition(EsdlSprite* sprite) {
    return sprite->x;
}

int esdlGetSpriteYPosition(EsdlSprite* sprite) {
    return sprite->y;
}

int esdlGetTotalHeight(const EsdlSprite* sprite) {
    return sprite->visiblePart.h;
}

int esdlGetTotalWidth(const EsdlSprite* sprite) {
    return sprite->visiblePart.w;
}

void esdlShowSprite(EsdlSprite* sprite) {
    sprite->show = true;
}

void esdlHideSprite(EsdlSprite* sprite) {
    sprite->show = false;
}

void esdlToggleSpriteVisibility(EsdlSprite* sprite) {
    sprite->show = (sprite->show ? false : true);
}

int esdlgetAbsoluteXPosition(const EsdlSprite* sprite) {
    int x = sprite->x;
    while (sprite->parent != NULL) {
        x += sprite->parent->x;
        sprite = sprite->parent;
    }
    return x;
}

int esdlGetAbsoluteYPosition(const EsdlSprite* sprite) {
    int y = sprite->y;
    while (sprite->parent != NULL) {
        y += sprite->parent->y;
        sprite = sprite->parent;
    }
    return y;
}

int esdlGetVisibleWidth(const EsdlSprite* sprite) {
    if (sprite->parent == NULL) {
        return sprite->visiblePart.w;
    }

    int width = esdlGetVisibleWidth(sprite->parent);
    width -= sprite->x;

    if (width < 0) {
        width = 0;
    }
    return (width < sprite->visiblePart.w) ? width : sprite->visiblePart.w;
}

int esdlGetVisibleHeight(const EsdlSprite* sprite) {
    if (sprite->parent == NULL) {
        return sprite->visiblePart.h;
    }
    
    int height = esdlGetVisibleHeight(sprite->parent);
    height -= sprite->y;
    
    if (height < 0) {
        height = 0;
    }
    return (height < sprite->visiblePart.h) ? height : sprite->visiblePart.h;
}

void esdlSetSpriteZIndex(EsdlSprite* sprite, char zindex) {
    sprite->zindex = zindex;
    if (sprite->parent != NULL) {
        removeElement(&(sprite->parent->children), (void *) sprite);
        _esdlInsertChild(&(sprite->parent->children), sprite);
    }
}

SDL_Surface* esdlGetSurface(EsdlSprite* sprite) {
    if (sprite->surface != NULL) {
        return sprite->surface;
    }
    return sprite->image->surface;
}

